#include <retro.h>
#include "player.frame.h"
#include "player.api.h"

using namespace Retro::Player;

#pragma region Macros

#define RETRO_SCRIPTINIT      _T("OnInit")
#define RETRO_SCRIPTUNINIT    _T("OnUninit")
#define RETRO_SCRIPTFRAME     _T("OnFrame")
#define RETRO_SCRIPTRENDER    _T("OnRender")

#define FRAME_STYLE \
  WS_POPUP | WS_SYSMENU | WS_MINIMIZEBOX | WS_DLGFRAME | WS_CAPTION

#define ERRMSG_PROJECT_NOT_FOUND    _T("Project not found")
#define ERRMSG_PROJECT_NOT_OPENED   _T("Unable to open project")

#pragma endregion

Frame* Frame::sFrame = NULL;

VOID AssertScript(Scripting::ScriptInterface* iInterface)
{
  if (iInterface->getError() != Scripting::ErrorNone)
  {
    PCTSTR wError = iInterface->getErrorMessage();

    if (!wError)
    {
      switch (iInterface->getError())
      {
      case Scripting::ErrorSyntax:
        wError = _T("Syntax error.");
        break;
      case Scripting::ErrorRuntime:
        wError = _T("Runtime error.");
        break;
      case Scripting::ErrorMemory:
        wError = _T("Memory error.");
        break;
      case Scripting::ErrorUnknown:
        wError = _T("Unknown error.");
        break;
      }
    }

    switch (MessageBox(
      NULL,
      wError, _T("Script Error"),
      MB_ICONERROR | MB_RETRYCANCEL))
    {
    case IDCANCEL:
      Frame::get()->stop();
      break;
    }
  }
}

Frame::Frame()
{
  mDisplay  = NULL;
  mAudio    = NULL;
  mPause    = FALSE;
  mEmbed    = FALSE;
}

WPARAM  Frame::start(LPCTSTR iFilename, BOOL iEmbed)
{
  Frame frame;
  WPARAM result = ERROR_SUCCESS;

  sFrame = &frame;

  frame.mEmbed = iEmbed;

  if (IsValidPath(iFilename))
  {
    _tcscpy_s(frame.mProjectPath, MAX_PATH, iFilename);
    frame.openProject(frame.mProjectPath);
  }
  else if (IsValidPath(RETRO_PROJECTFILE))
  {
    _tcscpy_s(frame.mProjectPath, MAX_PATH, RETRO_PROJECTFILE);
    frame.openProject(frame.mProjectPath);
  }
  else
  {
    MessageBox(NULL, ERRMSG_PROJECT_NOT_FOUND, NULL, MB_ICONERROR);
    result = ERROR_FILE_NOT_FOUND;
  }

  sFrame = NULL;
  return result;
}

BOOL  Frame::openProject(PCTSTR path)
{
  mEditor = FindWindow(RETRO_CLASS_EDITOR, NULL);

  if (!mProject.open(path))
  {
    MessageBox(*this, ERRMSG_PROJECT_NOT_OPENED, NULL, MB_ICONERROR);
    return FALSE;
  }

  registerClass(RETRO_CLASS_PLAYER, CS_OWNDC);
  create(RETRO_CLASS_PLAYER, mProject.getName(), 0, 0, 0, 0);

  activate();
  initDisplay();
  initAudio();

  mEditor.post(RETRO_PLAYERMSG_PLAYERREADY, NULL, NULL);

  if (mEmbed)
    embed();
  else
    detach();

  runGameLoop();
  unregisterClass(RETRO_CLASS_PLAYER);

  mEditor.post(RETRO_PLAYERMSG_PLAYERCLOSING, NULL, NULL);

  delete mDisplay;
  delete mAudio;

  return TRUE;
}

VOID  Frame::closeProject()
{
  mProject.close();
  destroy();
}

VOID  Frame::initDisplay()
{
  mDisplay = Display::CreateDevice(LibraryD3D);
  mDisplay->setWindow(*this);
  mDisplay->setWidth(mProject.getGameWidth());
  mDisplay->setHeight(mProject.getGameHeight());
}

VOID  Frame::initAudio()
{
  mAudio = Audio::CreateDevice(LibraryFMOD);
  mAudio->initialize();
}

VOID  Frame::runGameLoop()
{
  Scripting::ScriptManager wManager;
  Scripting::ScriptInterface& wInterface =
    *wManager.getInterface(Data::ScriptLua);

  API::InitializeAPI(&wManager);

  Data::Script* wScriptInit = (Data::Script*)
    getProject().findResource(Data::ResourceScript, RETRO_SCRIPTINIT);
  Data::Script* wScriptUninit = (Data::Script*)
    getProject().findResource(Data::ResourceScript, RETRO_SCRIPTUNINIT);
  Data::Script* wScriptFrame = (Data::Script*)
    getProject().findResource(Data::ResourceScript, RETRO_SCRIPTFRAME);
  Data::Script* wScriptRender = (Data::Script*)
    getProject().findResource(Data::ResourceScript, RETRO_SCRIPTRENDER);

  LARGE_INTEGER wTimeFreq;
  LARGE_INTEGER wRefTime;
  LARGE_INTEGER wTime;

  mDeltaTime = 0;
  mRun = TRUE;

  QueryPerformanceFrequency(&wTimeFreq);

  if (wScriptInit)
  {
    wInterface.run(*wScriptInit);
    AssertScript(&wInterface);
  }

  QueryPerformanceCounter(&wTime);
  mLastTime = wTime.QuadPart;

  while (mRun)
  {
    MSG msg;

    QueryPerformanceCounter(&wRefTime);

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    if (mRun)
    {
      while ((mPause || !mDisplay->isInitialized()) && 
        GetMessage(&msg, *this, 0, 0) > 0)
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }

      QueryPerformanceCounter(&wTime);
      mLastTime += (wTime.QuadPart - wRefTime.QuadPart);

      wInterface.run(*wScriptFrame);
      AssertScript(&wInterface);

      mDisplay->beginScene();
      mDisplay->clear(0x000000);

      wInterface.run(*wScriptRender);
      AssertScript(&wInterface);

      mDisplay->endScene();

      QueryPerformanceCounter(&wTime);

      mDeltaTime  = (DOUBLE)(wTime.QuadPart - mLastTime) / wTimeFreq.QuadPart;
      mLastTime   = wTime.QuadPart;
    }
  }

  if (wScriptUninit)
  {
    wInterface.run(*wScriptUninit);
    AssertScript(&wInterface);
  }

  API::UninitializeAPI();
}

VOID  Frame::resetFrame()
{
  setStyle(FRAME_STYLE);
  setParent(NULL);

  setClientSize(
    getProject().getGameWidth(),
    getProject().getGameHeight());

  centerToScreen();
  show();
  activate();
}

VOID  Frame::onDestroy()
{
  if (!mEmbed)
    mRun = FALSE;
}

VOID  Frame::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  *iEvent.handled = TRUE;
}

VOID  Frame::onMouseDown(Event::MouseEvent& iEvent)
{
  mPause = FALSE;

  activate();
  focus();

  mEditor.post(RETRO_PLAYERMSG_PLAYERACTIVATED, NULL, NULL);
}

VOID  Frame::onActivateApp(Event::ActivateAppEvent& iEvent)
{
  if (iEvent.activated)
  {
    mPause = FALSE;
    focus();
  }
  else
  {
    mPause = TRUE;
  }
}

VOID  Frame::wndProc(Event::WndProcEvent& iEvent)
{
  switch (iEvent.msg)
  {
  case RETRO_EDITORMSG_PREEMBED:
    embed();
    break;
  case RETRO_EDITORMSG_POSTEMBED:
    initialize();
    break;
  case RETRO_EDITORMSG_DETACH:
    detach();
    break;
  case RETRO_EDITORMSG_DEBUGPAUSE:
    mPause = TRUE;
    break;
  case RETRO_EDITORMSG_DEBUGRESUME:
    mPause = FALSE;
    PostQuitMessage(0);
    break;
  case RETRO_EDITORMSG_SETFULLSCREEN:
    setFullscreen(TRUE);
    break;
  default:
    FrameWnd::wndProc(iEvent);
  }
}

VOID  Frame::embed()
{
  setStyle((getStyle() & ~WS_OVERLAPPEDWINDOW) | WS_CHILD | WS_CLIPSIBLINGS);
  hide();

  mPause = TRUE;
  mEmbed = TRUE;
}

VOID  Frame::detach()
{
  resetFrame();
  initialize();

  mEmbed = FALSE;
  mPause = FALSE;
}

VOID  Frame::initialize()
{
  show();
  focus();

  if (!mDisplay->isInitialized())
    mDisplay->initialize();

  mPause = FALSE;
}

VOID  Frame::setFullscreen(BOOL iFullscreen)
{
  if (iFullscreen)
  {
    if (mEmbed)
    {
      mEditor.post(RETRO_PLAYERMSG_PLAYERFULLSCREEN, NULL, NULL);
    }
    else
    {
      Geom::Size wSize;

      mDisplay->getDisplaySize(wSize);
      mDisplay->setWidth(wSize.cx);
      mDisplay->setHeight(wSize.cy);
      mDisplay->setDisplayMode(Display::DisplayFullscreen);
    }
  }
  else
  {
    resetFrame();

    mDisplay->setWidth(mProject.getGameWidth());
    mDisplay->setHeight(mProject.getGameHeight());
    mDisplay->setDisplayMode(Display::DisplayWindowed);
  }

  mPause = FALSE;
}
